﻿using System;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;

using DatabaseGroup.SSIS.SourceAdapters.Parser;

namespace DatabaseGroup.UnitTests.SSIS.SourceAdapters.Parser
{
    [TestClass()]
    public class ParsingStateTest
    {
        private TestContext _testContextInstance;

        public TestContext TestContext
        {
            get
            {
                return _testContextInstance;
            }
            set
            {
                _testContextInstance = value;
            }
        }
        
        [TestMethod()]
        [ExpectedException(typeof(ArgumentNullException), "Expected ArgumentNullException to be thrown when called with null arg")]
        public void AddNextState_WithNullArg_ThrowArgumentNullException()
        {
            ParsingState target = new ParsingState();
            target.SetErrorState(null);
            target.AddNextState(null);
        }

        [TestMethod()]
        [ExpectedException(typeof(ArgumentNullException), "Expected ArgumentNullException to be thrown when called with null 1st arg")]
        public void ProcessCharacter_WithNull1stArg_ThrowArgumentNullException()
        {
            ParsingState target = new ParsingState();
            target.ProcessCharacter(null, 'a');
        }

        [TestMethod()]
        public void ProcessCharacter_ProcessDataWithoutNextState_ReturnExpectedMatchParsingResult()
        {
            ParsingContextTestImpl context = new ParsingContextTestImpl();
            ParsingState target = new ParsingState();
            FileReaderTestImpl reader = new FileReaderTestImpl(50);
            StringBuilder localText = new StringBuilder();
            while (!reader.IsEOF)
            {
                char nextChar = reader.GetNextChar();
                localText.Append(nextChar);
                Assert.AreEqual(ParsingResult.Match, target.ProcessCharacter(context, nextChar), "Expected processing of char to return Match result based on no states being defined/met");
                Assert.AreEqual<string>(localText.ToString(), context.CurrentText, "Expected parsed text within context to be same as local text string provided for processing");
            }
        }

        [TestMethod()]
        public void ProcessCharacter_ProcessDataWithLinkedStates_ReturnExpectedParsingResult()
        {
            TestLinkedStates(new ParsingResult[] { ParsingResult.Match }, 0);
            TestLinkedStates(new ParsingResult[] { ParsingResult.Miss }, -1);
            TestLinkedStates(new ParsingResult[] { ParsingResult.Match, ParsingResult.Miss }, 0);
            TestLinkedStates(new ParsingResult[] { ParsingResult.Miss, ParsingResult.Miss, ParsingResult.Match }, 2);
            TestLinkedStates(new ParsingResult[] { ParsingResult.Miss, ParsingResult.Miss, ParsingResult.Miss }, -1);
            TestLinkedStates(new ParsingResult[] { ParsingResult.Miss, ParsingResult.Done, ParsingResult.Match }, 1);
        }

        /// <summary>
        /// Sets up a ParsingState with a list of "next states" and 
        /// then processes a random char, asserting the result equals the expected state
        /// </summary>
        /// <param name="returnResults">The list of parsing results that each added state will return</param>
        /// <param name="transitionIndex">
        /// The zero-based index of the results that the state should change to after processing the char
        /// (-1 indicates a default value of Match)
        /// </param>
        private static void TestLinkedStates(ParsingResult [] returnResults, int transitionIndex)
        {
            ParsingState target = new ParsingState();
            ParsingResult finalResult = ParsingResult.Match;
            IParsingState expectedState = target;

            for (int i = 0; i < returnResults.Length; i++)
            {
                ParsingResult result = returnResults[i];
                ParsingStateTestImpl nextState = new ParsingStateTestImpl(result);
                target.AddNextState(nextState);
                if (i == transitionIndex)
                {
                    finalResult = result;
                    if (finalResult != ParsingResult.Miss)
                    {
                        expectedState = nextState;
                    }
                    else
                    {
                        expectedState = target;
                    }
                }
            }

            ParsingContextTestImpl context = new ParsingContextTestImpl();
            context.CurrentState = target;
            char randomChar = (char)(new Random().Next(char.MinValue, char.MaxValue));
            Assert.AreEqual<ParsingResult>(finalResult, target.ProcessCharacter(context, randomChar), 
                "Expected processing of char to return a different result based on the defined states");
            Assert.AreEqual<IParsingState>(expectedState, context.CurrentState,
                "Expected different context state to be returned based on defined states");
        }
    }
}
